1 : <?php
2 : /*--------------------------------------------------------------------------+
3 : This file is part of eStudy.
4 : common/classes/class.stringparser_bbcode.inc.php
5 : - Modulgruppe: Framework
6 : - Beschreibung: BBCode String-Parser
7 : - Version: 0.2, 24/04/06
8 : - Autor(en): Christian Seiler <spam@christian-seiler.de>
9 : Clemens Weiß <clemens.weiss@mni.fh-giessen.de>
10 : Daniel Knapp <daniel.knapp@mni.fh-giessen.de>
11 : +---------------------------------------------------------------------------+
12 : This program is free software; you can redistribute it and/or
13 : modify it under the terms of the GNU General Public License
14 : as published by the Free Software Foundation; either version 2
15 : of the License, or any later version.
16 : +---------------------------------------------------------------------------+
17 : This program is distributed in the hope that it will be useful,
18 : but WITHOUT ANY WARRANTY; without even the implied warranty of
19 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 : GNU General Public License for more details.
21 : You should have received a copy of the GNU General Public License
22 : Along with this program; if not, write to the Free Software
23 : Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 : +--------------------------------------------------------------------------*/
25 : /**
26 : * BB code string parsing class
27 : *
28 : * Version: 0.3.3
29 : *
30 : * @author Christian Seiler <spam@christian-seiler.de>
31 : * @copyright Christian Seiler 2004-2008
32 : * @package stringparser
33 : *
34 : * The MIT License
35 : *
36 : * Copyright (c) 2004-2008 Christian Seiler
37 : *
38 : * Permission is hereby granted, free of charge, to any person obtaining a copy
39 : * of this software and associated documentation files (the "Software"), to deal
40 : * in the Software without restriction, including without limitation the rights
41 : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42 : * copies of the Software, and to permit persons to whom the Software is
43 : * furnished to do so, subject to the following conditions:
44 : *
45 : * The above copyright notice and this permission notice shall be included in
46 : * all copies or substantial portions of the Software.
47 : *
48 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51 : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53 : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54 : * THE SOFTWARE.
55 : */
56 :
57 1 : require_once dirname(__FILE__).'/class.stringparser.inc.php';
58 :
59 1 : define ('BBCODE_CLOSETAG_FORBIDDEN', -1);
60 1 : define ('BBCODE_CLOSETAG_OPTIONAL', 0);
61 1 : define ('BBCODE_CLOSETAG_IMPLICIT', 1);
62 1 : define ('BBCODE_CLOSETAG_IMPLICIT_ON_CLOSE_ONLY', 2);
63 1 : define ('BBCODE_CLOSETAG_MUSTEXIST', 3);
64 :
65 1 : define ('BBCODE_NEWLINE_PARSE', 0);
66 1 : define ('BBCODE_NEWLINE_IGNORE', 1);
67 1 : define ('BBCODE_NEWLINE_DROP', 2);
68 :
69 1 : define ('BBCODE_PARAGRAPH_ALLOW_BREAKUP', 0);
70 1 : define ('BBCODE_PARAGRAPH_ALLOW_INSIDE', 1);
71 1 : define ('BBCODE_PARAGRAPH_BLOCK_ELEMENT', 2);
72 :
73 : /**
74 : * BB code string parser class
75 : *
76 : * @package stringparser
77 : */
78 1 : class StringParser_BBCode extends StringParser {
79 : /**
80 : * String parser mode
81 : *
82 : * The BBCode string parser works in search mode
83 : *
84 : * @access protected
85 : * @var int
86 : * @see STRINGPARSER_MODE_SEARCH, STRINGPARSER_MODE_LOOP
87 : */
88 : var $_parserMode = STRINGPARSER_MODE_SEARCH;
89 :
90 : /**
91 : * Defined BB Codes
92 : *
93 : * The registered BB codes
94 : *
95 : * @access protected
96 : * @var array
97 : */
98 : var $_codes = array ();
99 :
100 : /**
101 : * Registered parsers
102 : *
103 : * @access protected
104 : * @var array
105 : */
106 : var $_parsers = array ();
107 :
108 : /**
109 : * Defined maximum occurrences
110 : *
111 : * @access protected
112 : * @var array
113 : */
114 : var $_maxOccurrences = array ();
115 :
116 : /**
117 : * Root content type
118 : *
119 : * @access protected
120 : * @var string
121 : */
122 : var $_rootContentType = 'block';
123 :
124 : /**
125 : * Do not output but return the tree
126 : *
127 : * @access protected
128 : * @var bool
129 : */
130 : var $_noOutput = false;
131 :
132 : /**
133 : * Global setting: case sensitive
134 : *
135 : * @access protected
136 : * @var bool
137 : */
138 : var $_caseSensitive = true;
139 :
140 : /**
141 : * Root paragraph handling enabled
142 : *
143 : * @access protected
144 : * @var bool
145 : */
146 : var $_rootParagraphHandling = false;
147 :
148 : /**
149 : * Paragraph handling parameters
150 : * @access protected
151 : * @var array
152 : */
153 : var $_paragraphHandling = array (
154 : 'detect_string' => "\n\n",
155 : 'start_tag' => '<p>',
156 : 'end_tag' => "</p>\n"
157 : );
158 :
159 : /**
160 : * Allow mixed attribute types (e.g. [code=bla attr=blub])
161 : * @access private
162 : * @var bool
163 : */
164 : var $_mixedAttributeTypes = false;
165 :
166 : /**
167 : * Whether to call validation function again (with $action == 'validate_auto') when closetag comes
168 : * @access protected
169 : * @var bool
170 : */
171 : var $_validateAgain = false;
172 :
173 : /**
174 : * Add a code
175 : *
176 : * @access public
177 : * @param string $name The name of the code
178 : * @param string $callback_type See documentation
179 : * @param string $callback_func The callback function to call
180 : * @param array $callback_params The callback parameters
181 : * @param string $content_type See documentation
182 : * @param array $allowed_within See documentation
183 : * @param array $not_allowed_within See documentation
184 : * @return bool
185 : */
186 : function addCode ($name, $callback_type, $callback_func, $callback_params, $content_type, $allowed_within, $not_allowed_within) {
187 0 : if (isset ($this->_codes[$name])) {
188 0 : return false; // already exists
189 : }
190 0 : if (!preg_match ('/^[a-zA-Z0-9*_!+-]+$/', $name)) {
191 0 : return false; // invalid
192 : }
193 0 : $this->_codes[$name] = array (
194 0 : 'name' => $name,
195 0 : 'callback_type' => $callback_type,
196 0 : 'callback_func' => $callback_func,
197 0 : 'callback_params' => $callback_params,
198 0 : 'content_type' => $content_type,
199 0 : 'allowed_within' => $allowed_within,
200 0 : 'not_allowed_within' => $not_allowed_within,
201 0 : 'flags' => array ()
202 0 : );
203 0 : return true;
204 : }
205 :
206 : /**
207 : * Remove a code
208 : *
209 : * @access public
210 : * @param $name The code to remove
211 : * @return bool
212 : */
213 : function removeCode ($name) {
214 0 : if (isset ($this->_codes[$name])) {
215 0 : unset ($this->_codes[$name]);
216 0 : return true;
217 : }
218 0 : return false;
219 : }
220 :
221 : /**
222 : * Remove all codes
223 : *
224 : * @access public
225 : */
226 : function removeAllCodes () {
227 0 : $this->_codes = array ();
228 0 : }
229 :
230 : /**
231 : * Set a code flag
232 : *
233 : * @access public
234 : * @param string $name The name of the code
235 : * @param string $flag The name of the flag to set
236 : * @param mixed $value The value of the flag to set
237 : * @return bool
238 : */
239 : function setCodeFlag ($name, $flag, $value) {
240 0 : if (!isset ($this->_codes[$name])) {
241 0 : return false;
242 : }
243 0 : $this->_codes[$name]['flags'][$flag] = $value;
244 0 : return true;
245 : }
246 :
247 : /**
248 : * Set occurrence type
249 : *
250 : * Example:
251 : * $bbcode->setOccurrenceType ('url', 'link');
252 : * $bbcode->setMaxOccurrences ('link', 4);
253 : * Would create the situation where a link may only occur four
254 : * times in the hole text.
255 : *
256 : * @access public
257 : * @param string $code The name of the code
258 : * @param string $type The name of the occurrence type to set
259 : * @return bool
260 : */
261 : function setOccurrenceType ($code, $type) {
262 0 : return $this->setCodeFlag ($code, 'occurrence_type', $type);
263 : }
264 :
265 : /**
266 : * Set maximum number of occurrences
267 : *
268 : * @access public
269 : * @param string $type The name of the occurrence type
270 : * @param int $count The maximum number of occurrences
271 : * @return bool
272 : */
273 : function setMaxOccurrences ($type, $count) {
274 0 : settype ($count, 'integer');
275 0 : if ($count < 0) { // sorry, does not make any sense
276 0 : return false;
277 : }
278 0 : $this->_maxOccurrences[$type] = $count;
279 0 : return true;
280 : }
281 :
282 : /**
283 : * Add a parser
284 : *
285 : * @access public
286 : * @param string $type The content type for which the parser is to add
287 : * @param mixed $parser The function to call
288 : * @return bool
289 : */
290 : function addParser ($type, $parser) {
291 0 : if (is_array ($type)) {
292 0 : foreach ($type as $t) {
293 0 : $this->addParser ($t, $parser);
294 0 : }
295 0 : return true;
296 : }
297 0 : if (!isset ($this->_parsers[$type])) {
298 0 : $this->_parsers[$type] = array ();
299 0 : }
300 0 : $this->_parsers[$type][] = $parser;
301 0 : return true;
302 : }
303 :
304 : /**
305 : * Set root content type
306 : *
307 : * @access public
308 : * @param string $content_type The new root content type
309 : */
310 : function setRootContentType ($content_type) {
311 0 : $this->_rootContentType = $content_type;
312 0 : }
313 :
314 : /**
315 : * Set paragraph handling on root element
316 : *
317 : * @access public
318 : * @param bool $enabled The new status of paragraph handling on root element
319 : */
320 : function setRootParagraphHandling ($enabled) {
321 0 : $this->_rootParagraphHandling = (bool)$enabled;
322 0 : }
323 :
324 : /**
325 : * Set paragraph handling parameters
326 : *
327 : * @access public
328 : * @param string $detect_string The string to detect
329 : * @param string $start_tag The replacement for the start tag (e.g. <p>)
330 : * @param string $end_tag The replacement for the start tag (e.g. </p>)
331 : */
332 : function setParagraphHandlingParameters ($detect_string, $start_tag, $end_tag) {
333 0 : $this->_paragraphHandling = array (
334 0 : 'detect_string' => $detect_string,
335 0 : 'start_tag' => $start_tag,
336 : 'end_tag' => $end_tag
337 0 : );
338 0 : }
339 :
340 : /**
341 : * Set global case sensitive flag
342 : *
343 : * If this is set to true, the class normally is case sensitive, but
344 : * the case_sensitive code flag may override this for a single code.
345 : *
346 : * If this is set to false, all codes are case insensitive.
347 : *
348 : * @access public
349 : * @param bool $caseSensitive
350 : */
351 : function setGlobalCaseSensitive ($caseSensitive) {
352 0 : $this->_caseSensitive = (bool)$caseSensitive;
353 0 : }
354 :
355 : /**
356 : * Get global case sensitive flag
357 : *
358 : * @access public
359 : * @return bool
360 : */
361 : function globalCaseSensitive () {
362 0 : return $this->_caseSensitive;
363 : }
364 :
365 : /**
366 : * Set mixed attribute types flag
367 : *
368 : * If set, [code=val1 attr=val2] will cause 2 attributes to be parsed:
369 : * 'default' will have value 'val1', 'attr' will have value 'val2'.
370 : * If not set, only one attribute 'default' will have the value
371 : * 'val1 attr=val2' (the default and original behaviour)
372 : *
373 : * @access public
374 : * @param bool $mixedAttributeTypes
375 : */
376 : function setMixedAttributeTypes ($mixedAttributeTypes) {
377 0 : $this->_mixedAttributeTypes = (bool)$mixedAttributeTypes;
378 0 : }
379 :
380 : /**
381 : * Get mixed attribute types flag
382 : *
383 : * @access public
384 : * @return bool
385 : */
386 : function mixedAttributeTypes () {
387 0 : return $this->_mixedAttributeTypes;
388 : }
389 :
390 : /**
391 : * Set validate again flag
392 : *
393 : * If this is set to true, the class calls the validation function
394 : * again with $action == 'validate_again' when closetag comes.
395 : *
396 : * @access public
397 : * @param bool $validateAgain
398 : */
399 : function setValidateAgain ($validateAgain) {
400 0 : $this->_validateAgain = (bool)$validateAgain;
401 0 : }
402 :
403 : /**
404 : * Get validate again flag
405 : *
406 : * @access public
407 : * @return bool
408 : */
409 : function validateAgain () {
410 0 : return $this->_validateAgain;
411 : }
412 :
413 : /**
414 : * Get a code flag
415 : *
416 : * @access public
417 : * @param string $name The name of the code
418 : * @param string $flag The name of the flag to get
419 : * @param string $type The type of the return value
420 : * @param mixed $default The default return value
421 : * @return bool
422 : */
423 : function getCodeFlag ($name, $flag, $type = 'mixed', $default = null) {
424 0 : if (!isset ($this->_codes[$name])) {
425 0 : return $default;
426 : }
427 0 : if (!array_key_exists ($flag, $this->_codes[$name]['flags'])) {
428 0 : return $default;
429 : }
430 0 : $return = $this->_codes[$name]['flags'][$flag];
431 0 : if ($type != 'mixed') {
432 0 : settype ($return, $type);
433 0 : }
434 0 : return $return;
435 : }
436 :
437 : /**
438 : * Set a specific status
439 : * @access protected
440 : */
441 : function _setStatus ($status) {
442 : switch ($status) {
443 0 : case 0:
444 0 : $this->_charactersSearch = array ('[/', '[');
445 0 : $this->_status = $status;
446 0 : break;
447 0 : case 1:
448 0 : $this->_charactersSearch = array (']', ' = "', '="', ' = \'', '=\'', ' = ', '=', ': ', ':', ' ');
449 0 : $this->_status = $status;
450 0 : break;
451 0 : case 2:
452 0 : $this->_charactersSearch = array (']');
453 0 : $this->_status = $status;
454 0 : $this->_savedName = '';
455 0 : break;
456 0 : case 3:
457 0 : if ($this->_quoting !== null) {
458 0 : if ($this->_mixedAttributeTypes) {
459 0 : $this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.' ', $this->_quoting.']', $this->_quoting);
460 0 : } else {
461 0 : $this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.']', $this->_quoting);
462 : }
463 0 : $this->_status = $status;
464 0 : break;
465 : }
466 0 : if ($this->_mixedAttributeTypes) {
467 0 : $this->_charactersSearch = array (' ', ']');
468 0 : } else {
469 0 : $this->_charactersSearch = array (']');
470 : }
471 0 : $this->_status = $status;
472 0 : break;
473 0 : case 4:
474 0 : $this->_charactersSearch = array (' ', ']', '="', '=\'', '=');
475 0 : $this->_status = $status;
476 0 : $this->_savedName = '';
477 0 : $this->_savedValue = '';
478 0 : break;
479 0 : case 5:
480 0 : if ($this->_quoting !== null) {
481 0 : $this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.' ', $this->_quoting.']', $this->_quoting);
482 0 : } else {
483 0 : $this->_charactersSearch = array (' ', ']');
484 : }
485 0 : $this->_status = $status;
486 0 : $this->_savedValue = '';
487 0 : break;
488 0 : case 7:
489 0 : $this->_charactersSearch = array ('[/'.$this->_topNode ('name').']');
490 0 : if (!$this->_topNode ('getFlag', 'case_sensitive', 'boolean', true) || !$this->_caseSensitive) {
491 0 : $this->_charactersSearch[] = '[/';
492 0 : }
493 0 : $this->_status = $status;
494 0 : break;
495 0 : default:
496 0 : return false;
497 0 : }
498 0 : return true;
499 : }
500 :
501 : /**
502 : * Abstract method Append text depending on current status
503 : * @access protected
504 : * @param string $text The text to append
505 : * @return bool On success, the function returns true, else false
506 : */
507 : function _appendText ($text) {
508 0 : if (!strlen ($text)) {
509 0 : return true;
510 : }
511 0 : switch ($this->_status) {
512 0 : case 0:
513 0 : case 7:
514 0 : return $this->_appendToLastTextChild ($text);
515 0 : case 1:
516 0 : return $this->_topNode ('appendToName', $text);
517 0 : case 2:
518 0 : case 4:
519 0 : $this->_savedName .= $text;
520 0 : return true;
521 0 : case 3:
522 0 : return $this->_topNode ('appendToAttribute', 'default', $text);
523 0 : case 5:
524 0 : $this->_savedValue .= $text;
525 0 : return true;
526 0 : default:
527 0 : return false;
528 0 : }
529 : }
530 :
531 : /**
532 : * Restart parsing after current block
533 : *
534 : * To achieve this the current top stack object is removed from the
535 : * tree. Then the current item
536 : *
537 : * @access protected
538 : * @return bool
539 : */
540 : function _reparseAfterCurrentBlock () {
541 0 : if ($this->_status == 2) {
542 : // this status will *never* call _reparseAfterCurrentBlock itself
543 : // so this is called if the loop ends
544 : // therefore, just add the [/ to the text
545 :
546 : // _savedName should be empty but just in case
547 0 : $this->_cpos -= strlen ($this->_savedName);
548 0 : $this->_savedName = '';
549 0 : $this->_status = 0;
550 0 : $this->_appendText ('[/');
551 0 : return true;
552 : } else {
553 0 : return parent::_reparseAfterCurrentBlock ();
554 : }
555 : }
556 :
557 : /**
558 : * Apply parsers
559 : */
560 : function _applyParsers ($type, $text) {
561 0 : if (!isset ($this->_parsers[$type])) {
562 0 : return $text;
563 : }
564 0 : foreach ($this->_parsers[$type] as $parser) {
565 0 : if (is_callable ($parser)) {
566 0 : $ntext = call_user_func ($parser, $text);
567 0 : if (is_string ($ntext)) {
568 0 : $text = $ntext;
569 0 : }
570 0 : }
571 0 : }
572 0 : return $text;
573 : }
574 :
575 : /**
576 : * Handle status
577 : * @access protected
578 : * @param int $status The current status
579 : * @param string $needle The needle that was found
580 : * @return bool
581 : */
582 : function _handleStatus ($status, $needle) {
583 : switch ($status) {
584 0 : case 0: // NORMAL TEXT
585 0 : if ($needle != '[' && $needle != '[/') {
586 0 : $this->_appendText ($needle);
587 0 : return true;
588 : }
589 0 : if ($needle == '[') {
590 0 : $node = new StringParser_BBCode_Node_Element ($this->_cpos);
591 0 : $res = $this->_pushNode ($node);
592 0 : if (!$res) {
593 0 : return false;
594 : }
595 0 : $this->_setStatus (1);
596 0 : } else if ($needle == '[/') {
597 0 : if (count ($this->_stack) <= 1) {
598 0 : $this->_appendText ($needle);
599 0 : return true;
600 : }
601 0 : $this->_setStatus (2);
602 0 : }
603 0 : break;
604 0 : case 1: // OPEN TAG
605 0 : if ($needle == ']') {
606 0 : return $this->_openElement (0);
607 0 : } else if (trim ($needle) == ':' || trim ($needle) == '=') {
608 0 : $this->_quoting = null;
609 0 : $this->_setStatus (3); // default value parser
610 0 : break;
611 0 : } else if (trim ($needle) == '="' || trim ($needle) == '= "' || trim ($needle) == '=\'' || trim ($needle) == '= \'') {
612 0 : $this->_quoting = substr (trim ($needle), -1);
613 0 : $this->_setStatus (3); // default value parser with quotation
614 0 : break;
615 0 : } else if ($needle == ' ') {
616 0 : $this->_setStatus (4); // attribute parser
617 0 : break;
618 : } else {
619 0 : $this->_appendText ($needle);
620 0 : return true;
621 : }
622 : // break not necessary because every if clause contains return
623 0 : case 2: // CLOSE TAG
624 0 : if ($needle != ']') {
625 0 : $this->_appendText ($needle);
626 0 : return true;
627 : }
628 0 : $closecount = 0;
629 0 : if (!$this->_isCloseable ($this->_savedName, $closecount)) {
630 0 : $this->_setStatus (0);
631 0 : $this->_appendText ('[/'.$this->_savedName.$needle);
632 0 : return true;
633 : }
634 : // this validates the code(s) to be closed after the content tree of
635 : // that code(s) are built - if the second validation fails, we will have
636 : // to reparse. note that as _reparseAfterCurrentBlock will not work correctly
637 : // if we're in $status == 2, we will have to set our status to 0 manually
638 0 : if (!$this->_validateCloseTags ($closecount)) {
639 0 : $this->_setStatus (0);
640 0 : return $this->_reparseAfterCurrentBlock ();
641 : }
642 0 : $this->_setStatus (0);
643 0 : for ($i = 0; $i < $closecount; $i++) {
644 0 : if ($i == $closecount - 1) {
645 0 : $this->_topNode ('setHadCloseTag');
646 0 : }
647 0 : if (!$this->_popNode ()) {
648 0 : return false;
649 : }
650 0 : }
651 0 : break;
652 0 : case 3: // DEFAULT ATTRIBUTE
653 0 : if ($this->_quoting !== null) {
654 0 : if ($needle == '\\\\') {
655 0 : $this->_appendText ('\\');
656 0 : return true;
657 0 : } else if ($needle == '\\'.$this->_quoting) {
658 0 : $this->_appendText ($this->_quoting);
659 0 : return true;
660 0 : } else if ($needle == $this->_quoting.' ') {
661 0 : $this->_setStatus (4);
662 0 : return true;
663 0 : } else if ($needle == $this->_quoting.']') {
664 0 : return $this->_openElement (2);
665 0 : } else if ($needle == $this->_quoting) {
666 : // can't be, only ']' and ' ' allowed after quoting char
667 0 : return $this->_reparseAfterCurrentBlock ();
668 : } else {
669 0 : $this->_appendText ($needle);
670 0 : return true;
671 : }
672 : } else {
673 0 : if ($needle == ' ') {
674 0 : $this->_setStatus (4);
675 0 : return true;
676 0 : } else if ($needle == ']') {
677 0 : return $this->_openElement (2);
678 : } else {
679 0 : $this->_appendText ($needle);
680 0 : return true;
681 : }
682 : }
683 : // break not needed because every if clause contains return!
684 0 : case 4: // ATTRIBUTE NAME
685 0 : if ($needle == ' ') {
686 0 : if (strlen ($this->_savedName)) {
687 0 : $this->_topNode ('setAttribute', $this->_savedName, true);
688 0 : }
689 : // just ignore and continue in same mode
690 0 : $this->_setStatus (4); // reset parameters
691 0 : return true;
692 0 : } else if ($needle == ']') {
693 0 : if (strlen ($this->_savedName)) {
694 0 : $this->_topNode ('setAttribute', $this->_savedName, true);
695 0 : }
696 0 : return $this->_openElement (2);
697 0 : } else if ($needle == '=') {
698 0 : $this->_quoting = null;
699 0 : $this->_setStatus (5);
700 0 : return true;
701 0 : } else if ($needle == '="') {
702 0 : $this->_quoting = '"';
703 0 : $this->_setStatus (5);
704 0 : return true;
705 0 : } else if ($needle == '=\'') {
706 0 : $this->_quoting = '\'';
707 0 : $this->_setStatus (5);
708 0 : return true;
709 : } else {
710 0 : $this->_appendText ($needle);
711 0 : return true;
712 : }
713 : // break not needed because every if clause contains return!
714 0 : case 5: // ATTRIBUTE VALUE
715 0 : if ($this->_quoting !== null) {
716 0 : if ($needle == '\\\\') {
717 0 : $this->_appendText ('\\');
718 0 : return true;
719 0 : } else if ($needle == '\\'.$this->_quoting) {
720 0 : $this->_appendText ($this->_quoting);
721 0 : return true;
722 0 : } else if ($needle == $this->_quoting.' ') {
723 0 : $this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
724 0 : $this->_setStatus (4);
725 0 : return true;
726 0 : } else if ($needle == $this->_quoting.']') {
727 0 : $this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
728 0 : return $this->_openElement (2);
729 0 : } else if ($needle == $this->_quoting) {
730 : // can't be, only ']' and ' ' allowed after quoting char
731 0 : return $this->_reparseAfterCurrentBlock ();
732 : } else {
733 0 : $this->_appendText ($needle);
734 0 : return true;
735 : }
736 : } else {
737 0 : if ($needle == ' ') {
738 0 : $this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
739 0 : $this->_setStatus (4);
740 0 : return true;
741 0 : } else if ($needle == ']') {
742 0 : $this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
743 0 : return $this->_openElement (2);
744 : } else {
745 0 : $this->_appendText ($needle);
746 0 : return true;
747 : }
748 : }
749 : // break not needed because every if clause contains return!
750 0 : case 7:
751 0 : if ($needle == '[/') {
752 : // this was case insensitive match
753 0 : if (strtolower (substr ($this->_text, $this->_cpos + strlen ($needle), strlen ($this->_topNode ('name')) + 1)) == strtolower ($this->_topNode ('name').']')) {
754 : // this matched
755 0 : $this->_cpos += strlen ($this->_topNode ('name')) + 1;
756 0 : } else {
757 : // it didn't match
758 0 : $this->_appendText ($needle);
759 0 : return true;
760 : }
761 0 : }
762 0 : $closecount = $this->_savedCloseCount;
763 0 : if (!$this->_topNode ('validate')) {
764 0 : return $this->_reparseAfterCurrentBlock ();
765 : }
766 : // do we have to close subnodes?
767 0 : if ($closecount) {
768 : // get top node
769 0 : $mynode =& $this->_stack[count ($this->_stack)-1];
770 : // close necessary nodes
771 0 : for ($i = 0; $i <= $closecount; $i++) {
772 0 : if (!$this->_popNode ()) {
773 0 : return false;
774 : }
775 0 : }
776 0 : if (!$this->_pushNode ($mynode)) {
777 0 : return false;
778 : }
779 0 : }
780 0 : $this->_setStatus (0);
781 0 : $this->_popNode ();
782 0 : return true;
783 0 : default:
784 0 : return false;
785 0 : }
786 0 : return true;
787 : }
788 :
789 : /**
790 : * Open the next element
791 : *
792 : * @access protected
793 : * @return bool
794 : */
795 : function _openElement ($type = 0) {
796 0 : $name = $this->_getCanonicalName ($this->_topNode ('name'));
797 0 : if ($name === false) {
798 0 : return $this->_reparseAfterCurrentBlock ();
799 : }
800 0 : $occ_type = $this->getCodeFlag ($name, 'occurrence_type', 'string');
801 0 : if ($occ_type !== null && isset ($this->_maxOccurrences[$occ_type])) {
802 0 : $max_occs = $this->_maxOccurrences[$occ_type];
803 0 : $occs = $this->_root->getNodeCountByCriterium ('flag:occurrence_type', $occ_type);
804 0 : if ($occs >= $max_occs) {
805 0 : return $this->_reparseAfterCurrentBlock ();
806 : }
807 0 : }
808 0 : $closecount = 0;
809 0 : $this->_topNode ('setCodeInfo', $this->_codes[$name]);
810 0 : if (!$this->_isOpenable ($name, $closecount)) {
811 0 : return $this->_reparseAfterCurrentBlock ();
812 : }
813 0 : $this->_setStatus (0);
814 : switch ($type) {
815 0 : case 0:
816 0 : $cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], false);
817 0 : break;
818 0 : case 1:
819 0 : $cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], true);
820 0 : break;
821 0 : case 2:
822 0 : $cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], true);
823 0 : break;
824 0 : default:
825 0 : $cond = false;
826 0 : break;
827 0 : }
828 0 : if ($cond) {
829 0 : $this->_savedCloseCount = $closecount;
830 0 : $this->_setStatus (7);
831 0 : return true;
832 : }
833 0 : if (!$this->_topNode ('validate')) {
834 0 : return $this->_reparseAfterCurrentBlock ();
835 : }
836 : // do we have to close subnodes?
837 0 : if ($closecount) {
838 : // get top node
839 0 : $mynode =& $this->_stack[count ($this->_stack)-1];
840 : // close necessary nodes
841 0 : for ($i = 0; $i <= $closecount; $i++) {
842 0 : if (!$this->_popNode ()) {
843 0 : return false;
844 : }
845 0 : }
846 0 : if (!$this->_pushNode ($mynode)) {
847 0 : return false;
848 : }
849 0 : }
850 :
851 0 : if ($this->_codes[$name]['callback_type'] == 'simple_replace_single' || $this->_codes[$name]['callback_type'] == 'callback_replace_single') {
852 0 : if (!$this->_popNode ()) {
853 0 : return false;
854 : }
855 0 : }
856 :
857 0 : return true;
858 : }
859 :
860 : /**
861 : * Is a node closeable?
862 : *
863 : * @access protected
864 : * @return bool
865 : */
866 : function _isCloseable ($name, &$closecount) {
867 0 : $node =& $this->_findNamedNode ($name, false);
868 0 : if ($node === false) {
869 0 : return false;
870 : }
871 0 : $scount = count ($this->_stack);
872 0 : for ($i = $scount - 1; $i > 0; $i--) {
873 0 : $closecount++;
874 0 : if ($this->_stack[$i]->equals ($node)) {
875 0 : return true;
876 : }
877 0 : if ($this->_stack[$i]->getFlag ('closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT) == BBCODE_CLOSETAG_MUSTEXIST) {
878 0 : return false;
879 : }
880 0 : }
881 0 : return false;
882 : }
883 :
884 : /**
885 : * Revalidate codes when close tags appear
886 : *
887 : * @access protected
888 : * @return bool
889 : */
890 : function _validateCloseTags ($closecount) {
891 0 : $scount = count ($this->_stack);
892 0 : for ($i = $scount - 1; $i >= $scount - $closecount; $i--) {
893 0 : if ($this->_validateAgain) {
894 0 : if (!$this->_stack[$i]->validate ('validate_again')) {
895 0 : return false;
896 : }
897 0 : }
898 0 : }
899 0 : return true;
900 : }
901 :
902 : /**
903 : * Is a node openable?
904 : *
905 : * @access protected
906 : * @return bool
907 : */
908 : function _isOpenable ($name, &$closecount) {
909 0 : if (!isset ($this->_codes[$name])) {
910 0 : return false;
911 : }
912 :
913 0 : $closecount = 0;
914 :
915 0 : $allowed_within = $this->_codes[$name]['allowed_within'];
916 0 : $not_allowed_within = $this->_codes[$name]['not_allowed_within'];
917 :
918 0 : $scount = count ($this->_stack);
919 0 : if ($scount == 2) { // top level element
920 0 : if (!in_array ($this->_rootContentType, $allowed_within)) {
921 0 : return false;
922 : }
923 0 : } else {
924 0 : if (!in_array ($this->_stack[$scount-2]->_codeInfo['content_type'], $allowed_within)) {
925 0 : return $this->_isOpenableWithClose ($name, $closecount);
926 : }
927 : }
928 :
929 0 : for ($i = 1; $i < $scount - 1; $i++) {
930 0 : if (in_array ($this->_stack[$i]->_codeInfo['content_type'], $not_allowed_within)) {
931 0 : return $this->_isOpenableWithClose ($name, $closecount);
932 : }
933 0 : }
934 :
935 0 : return true;
936 : }
937 :
938 : /**
939 : * Is a node openable by closing other nodes?
940 : *
941 : * @access protected
942 : * @return bool
943 : */
944 : function _isOpenableWithClose ($name, &$closecount) {
945 0 : $tnname = $this->_getCanonicalName ($this->_topNode ('name'));
946 0 : if (!in_array ($this->getCodeFlag ($tnname, 'closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT), array (BBCODE_CLOSETAG_FORBIDDEN, BBCODE_CLOSETAG_OPTIONAL))) {
947 0 : return false;
948 : }
949 0 : $node =& $this->_findNamedNode ($name, true);
950 0 : if ($node === false) {
951 0 : return false;
952 : }
953 0 : $scount = count ($this->_stack);
954 0 : if ($scount < 3) {
955 0 : return false;
956 : }
957 0 : for ($i = $scount - 2; $i > 0; $i--) {
958 0 : $closecount++;
959 0 : if ($this->_stack[$i]->equals ($node)) {
960 0 : return true;
961 : }
962 0 : if (in_array ($this->_stack[$i]->getFlag ('closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT), array (BBCODE_CLOSETAG_IMPLICIT_ON_CLOSE_ONLY, BBCODE_CLOSETAG_MUSTEXIST))) {
963 0 : return false;
964 : }
965 0 : if ($this->_validateAgain) {
966 0 : if (!$this->_stack[$i]->validate ('validate_again')) {
967 0 : return false;
968 : }
969 0 : }
970 0 : }
971 :
972 0 : return false;
973 : }
974 :
975 : /**
976 : * Abstract method: Close remaining blocks
977 : * @access protected
978 : */
979 : function _closeRemainingBlocks () {
980 : // everything closed
981 0 : if (count ($this->_stack) == 1) {
982 0 : return true;
983 : }
984 : // not everything close
985 0 : if ($this->strict) {
986 0 : return false;
987 : }
988 0 : while (count ($this->_stack) > 1) {
989 0 : if ($this->_topNode ('getFlag', 'closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT) == BBCODE_CLOSETAG_MUSTEXIST) {
990 0 : return false; // sorry
991 : }
992 0 : $res = $this->_popNode ();
993 0 : if (!$res) {
994 0 : return false;
995 : }
996 0 : }
997 0 : return true;
998 : }
999 :
1000 : /**
1001 : * Find a node with a specific name in stack
1002 : *
1003 : * @access protected
1004 : * @return mixed
1005 : */
1006 : function &_findNamedNode ($name, $searchdeeper = false) {
1007 0 : $lname = $this->_getCanonicalName ($name);
1008 0 : $case_sensitive = $this->_caseSensitive && $this->getCodeFlag ($lname, 'case_sensitive', 'boolean', true);
1009 0 : if ($case_sensitive) {
1010 0 : $name = strtolower ($name);
1011 0 : }
1012 0 : $scount = count ($this->_stack);
1013 0 : if ($searchdeeper) {
1014 0 : $scount--;
1015 0 : }
1016 0 : for ($i = $scount - 1; $i > 0; $i--) {
1017 0 : if (!$case_sensitive) {
1018 0 : $cmp_name = strtolower ($this->_stack[$i]->name ());
1019 0 : } else {
1020 0 : $cmp_name = $this->_stack[$i]->name ();
1021 : }
1022 0 : if ($cmp_name == $lname) {
1023 0 : return $this->_stack[$i];
1024 : }
1025 0 : }
1026 0 : $result = false;
1027 0 : return $result;
1028 : }
1029 :
1030 : /**
1031 : * Abstract method: Output tree
1032 : * @access protected
1033 : * @return bool
1034 : */
1035 : function _outputTree () {
1036 0 : if ($this->_noOutput) {
1037 0 : return true;
1038 : }
1039 0 : $output = $this->_outputNode ($this->_root);
1040 0 : if (is_string ($output)) {
1041 0 : $this->_output = $this->_applyPostfilters ($output);
1042 0 : unset ($output);
1043 0 : return true;
1044 : }
1045 :
1046 0 : return false;
1047 : }
1048 :
1049 : /**
1050 : * Output a node
1051 : * @access protected
1052 : * @return bool
1053 : */
1054 : function _outputNode (&$node) {
1055 0 : $output = '';
1056 0 : if ($node->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH || $node->_type == STRINGPARSER_BBCODE_NODE_ELEMENT || $node->_type == STRINGPARSER_NODE_ROOT) {
1057 0 : $ccount = count ($node->_children);
1058 0 : for ($i = 0; $i < $ccount; $i++) {
1059 0 : $suboutput = $this->_outputNode ($node->_children[$i]);
1060 0 : if (!is_string ($suboutput)) {
1061 0 : return false;
1062 : }
1063 0 : $output .= $suboutput;
1064 0 : }
1065 0 : if ($node->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
1066 0 : return $this->_paragraphHandling['start_tag'].$output.$this->_paragraphHandling['end_tag'];
1067 : }
1068 0 : if ($node->_type == STRINGPARSER_BBCODE_NODE_ELEMENT) {
1069 0 : return $node->getReplacement ($output);
1070 : }
1071 0 : return $output;
1072 0 : } else if ($node->_type == STRINGPARSER_NODE_TEXT) {
1073 0 : $output = $node->content;
1074 0 : $before = '';
1075 0 : $after = '';
1076 0 : $ol = strlen ($output);
1077 0 : switch ($node->getFlag ('newlinemode.begin', 'integer', BBCODE_NEWLINE_PARSE)) {
1078 0 : case BBCODE_NEWLINE_IGNORE:
1079 0 : if ($ol && $output{0} == "\n") {
1080 0 : $before = "\n";
1081 0 : }
1082 : // don't break!
1083 0 : case BBCODE_NEWLINE_DROP:
1084 0 : if ($ol && $output{0} == "\n") {
1085 0 : $output = substr ($output, 1);
1086 0 : $ol--;
1087 0 : }
1088 0 : break;
1089 0 : }
1090 0 : switch ($node->getFlag ('newlinemode.end', 'integer', BBCODE_NEWLINE_PARSE)) {
1091 0 : case BBCODE_NEWLINE_IGNORE:
1092 0 : if ($ol && $output{$ol-1} == "\n") {
1093 0 : $after = "\n";
1094 0 : }
1095 : // don't break!
1096 0 : case BBCODE_NEWLINE_DROP:
1097 0 : if ($ol && $output{$ol-1} == "\n") {
1098 0 : $output = substr ($output, 0, -1);
1099 0 : $ol--;
1100 0 : }
1101 0 : break;
1102 0 : }
1103 : // can't do anything
1104 0 : if ($node->_parent === null) {
1105 0 : return $before.$output.$after;
1106 : }
1107 0 : if ($node->_parent->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
1108 0 : $parent =& $node->_parent;
1109 0 : unset ($node);
1110 0 : $node =& $parent;
1111 0 : unset ($parent);
1112 : // if no parent for this paragraph
1113 0 : if ($node->_parent === null) {
1114 0 : return $before.$output.$after;
1115 : }
1116 0 : }
1117 0 : if ($node->_parent->_type == STRINGPARSER_NODE_ROOT) {
1118 0 : return $before.$this->_applyParsers ($this->_rootContentType, $output).$after;
1119 : }
1120 0 : if ($node->_parent->_type == STRINGPARSER_BBCODE_NODE_ELEMENT) {
1121 0 : return $before.$this->_applyParsers ($node->_parent->_codeInfo['content_type'], $output).$after;
1122 : }
1123 0 : return $before.$output.$after;
1124 : }
1125 0 : }
1126 :
1127 : /**
1128 : * Abstract method: Manipulate the tree
1129 : * @access protected
1130 : * @return bool
1131 : */
1132 : function _modifyTree () {
1133 : // first pass: try to do newline handling
1134 0 : $nodes =& $this->_root->getNodesByCriterium ('needsTextNodeModification', true);
1135 0 : $nodes_count = count ($nodes);
1136 0 : for ($i = 0; $i < $nodes_count; $i++) {
1137 0 : $v = $nodes[$i]->getFlag ('opentag.before.newline', 'integer', BBCODE_NEWLINE_PARSE);
1138 0 : if ($v != BBCODE_NEWLINE_PARSE) {
1139 0 : $n =& $nodes[$i]->findPrevAdjentTextNode ();
1140 0 : if (!is_null ($n)) {
1141 0 : $n->setFlag ('newlinemode.end', $v);
1142 0 : }
1143 0 : unset ($n);
1144 0 : }
1145 0 : $v = $nodes[$i]->getFlag ('opentag.after.newline', 'integer', BBCODE_NEWLINE_PARSE);
1146 0 : if ($v != BBCODE_NEWLINE_PARSE) {
1147 0 : $n =& $nodes[$i]->firstChildIfText ();
1148 0 : if (!is_null ($n)) {
1149 0 : $n->setFlag ('newlinemode.begin', $v);
1150 0 : }
1151 0 : unset ($n);
1152 0 : }
1153 0 : $v = $nodes[$i]->getFlag ('closetag.before.newline', 'integer', BBCODE_NEWLINE_PARSE);
1154 0 : if ($v != BBCODE_NEWLINE_PARSE) {
1155 0 : $n =& $nodes[$i]->lastChildIfText ();
1156 0 : if (!is_null ($n)) {
1157 0 : $n->setFlag ('newlinemode.end', $v);
1158 0 : }
1159 0 : unset ($n);
1160 0 : }
1161 0 : $v = $nodes[$i]->getFlag ('closetag.after.newline', 'integer', BBCODE_NEWLINE_PARSE);
1162 0 : if ($v != BBCODE_NEWLINE_PARSE) {
1163 0 : $n =& $nodes[$i]->findNextAdjentTextNode ();
1164 0 : if (!is_null ($n)) {
1165 0 : $n->setFlag ('newlinemode.begin', $v);
1166 0 : }
1167 0 : unset ($n);
1168 0 : }
1169 0 : }
1170 :
1171 : // second pass a: do paragraph handling on root element
1172 0 : if ($this->_rootParagraphHandling) {
1173 0 : $res = $this->_handleParagraphs ($this->_root);
1174 0 : if (!$res) {
1175 0 : return false;
1176 : }
1177 0 : }
1178 :
1179 : // second pass b: do paragraph handling on other elements
1180 0 : unset ($nodes);
1181 0 : $nodes =& $this->_root->getNodesByCriterium ('flag:paragraphs', true);
1182 0 : $nodes_count = count ($nodes);
1183 0 : for ($i = 0; $i < $nodes_count; $i++) {
1184 0 : $res = $this->_handleParagraphs ($nodes[$i]);
1185 0 : if (!$res) {
1186 0 : return false;
1187 : }
1188 0 : }
1189 :
1190 : // second pass c: search for empty paragraph nodes and remove them
1191 0 : unset ($nodes);
1192 0 : $nodes =& $this->_root->getNodesByCriterium ('empty', true);
1193 0 : $nodes_count = count ($nodes);
1194 0 : if (isset ($parent)) {
1195 0 : unset ($parent); $parent = null;
1196 0 : }
1197 0 : for ($i = 0; $i < $nodes_count; $i++) {
1198 0 : if ($nodes[$i]->_type != STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
1199 0 : continue;
1200 : }
1201 0 : unset ($parent);
1202 0 : $parent =& $nodes[$i]->_parent;
1203 0 : $parent->removeChild ($nodes[$i], true);
1204 0 : }
1205 :
1206 0 : return true;
1207 : }
1208 :
1209 : /**
1210 : * Handle paragraphs
1211 : * @access protected
1212 : * @param object $node The node to handle
1213 : * @return bool
1214 : */
1215 : function _handleParagraphs (&$node) {
1216 : // if this node is already a subnode of a paragraph node, do NOT
1217 : // do paragraph handling on this node!
1218 0 : if ($this->_hasParagraphAncestor ($node)) {
1219 0 : return true;
1220 : }
1221 0 : $dest_nodes = array ();
1222 0 : $last_node_was_paragraph = false;
1223 0 : $prevtype = STRINGPARSER_NODE_TEXT;
1224 0 : $paragraph = null;
1225 0 : while (count ($node->_children)) {
1226 0 : $mynode =& $node->_children[0];
1227 0 : $node->removeChild ($mynode);
1228 0 : $subprevtype = $prevtype;
1229 0 : $sub_nodes =& $this->_breakupNodeByParagraphs ($mynode);
1230 0 : for ($i = 0; $i < count ($sub_nodes); $i++) {
1231 0 : if (!$last_node_was_paragraph || ($prevtype == $sub_nodes[$i]->_type && ($i != 0 || $prevtype != STRINGPARSER_BBCODE_NODE_ELEMENT))) {
1232 0 : unset ($paragraph);
1233 0 : $paragraph = new StringParser_BBCode_Node_Paragraph ();
1234 0 : }
1235 0 : $prevtype = $sub_nodes[$i]->_type;
1236 0 : if ($sub_nodes[$i]->_type != STRINGPARSER_BBCODE_NODE_ELEMENT || $sub_nodes[$i]->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP) != BBCODE_PARAGRAPH_BLOCK_ELEMENT) {
1237 0 : $paragraph->appendChild ($sub_nodes[$i]);
1238 0 : $dest_nodes[] =& $paragraph;
1239 0 : $last_node_was_paragraph = true;
1240 0 : } else {
1241 0 : $dest_nodes[] =& $sub_nodes[$i];
1242 0 : $last_onde_was_paragraph = false;
1243 0 : unset ($paragraph);
1244 0 : $paragraph = new StringParser_BBCode_Node_Paragraph ();
1245 : }
1246 0 : }
1247 0 : }
1248 0 : $count = count ($dest_nodes);
1249 0 : for ($i = 0; $i < $count; $i++) {
1250 0 : $node->appendChild ($dest_nodes[$i]);
1251 0 : }
1252 0 : unset ($dest_nodes);
1253 0 : unset ($paragraph);
1254 0 : return true;
1255 : }
1256 :
1257 : /**
1258 : * Search for a paragraph node in tree in upward direction
1259 : * @access protected
1260 : * @param object $node The node to analyze
1261 : * @return bool
1262 : */
1263 : function _hasParagraphAncestor (&$node) {
1264 0 : if ($node->_parent === null) {
1265 0 : return false;
1266 : }
1267 0 : $parent =& $node->_parent;
1268 0 : if ($parent->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
1269 0 : return true;
1270 : }
1271 0 : return $this->_hasParagraphAncestor ($parent);
1272 : }
1273 :
1274 : /**
1275 : * Break up nodes
1276 : * @access protected
1277 : * @param object $node The node to break up
1278 : * @return array
1279 : */
1280 : function &_breakupNodeByParagraphs (&$node) {
1281 0 : $detect_string = $this->_paragraphHandling['detect_string'];
1282 0 : $dest_nodes = array ();
1283 : // text node => no problem
1284 0 : if ($node->_type == STRINGPARSER_NODE_TEXT) {
1285 0 : $cpos = 0;
1286 0 : while (($npos = strpos ($node->content, $detect_string, $cpos)) !== false) {
1287 0 : $subnode = new StringParser_Node_Text (substr ($node->content, $cpos, $npos - $cpos), $node->occurredAt + $cpos);
1288 : // copy flags
1289 0 : foreach ($node->_flags as $flag => $value) {
1290 0 : if ($flag == 'newlinemode.begin') {
1291 0 : if ($cpos == 0) {
1292 0 : $subnode->setFlag ($flag, $value);
1293 0 : }
1294 0 : } else if ($flag == 'newlinemode.end') {
1295 : // do nothing
1296 0 : } else {
1297 0 : $subnode->setFlag ($flag, $value);
1298 : }
1299 0 : }
1300 0 : $dest_nodes[] =& $subnode;
1301 0 : unset ($subnode);
1302 0 : $cpos = $npos + strlen ($detect_string);
1303 0 : }
1304 0 : $subnode = new StringParser_Node_Text (substr ($node->content, $cpos), $node->occurredAt + $cpos);
1305 0 : if ($cpos == 0) {
1306 0 : $value = $node->getFlag ('newlinemode.begin', 'integer', null);
1307 0 : if ($value !== null) {
1308 0 : $subnode->setFlag ('newlinemode.begin', $value);
1309 0 : }
1310 0 : }
1311 0 : $value = $node->getFlag ('newlinemode.end', 'integer', null);
1312 0 : if ($value !== null) {
1313 0 : $subnode->setFlag ('newlinemode.end', $value);
1314 0 : }
1315 0 : $dest_nodes[] =& $subnode;
1316 0 : unset ($subnode);
1317 0 : return $dest_nodes;
1318 : }
1319 : // not a text node or an element node => no way
1320 0 : if ($node->_type != STRINGPARSER_BBCODE_NODE_ELEMENT) {
1321 0 : $dest_nodes[] =& $node;
1322 0 : return $dest_nodes;
1323 : }
1324 0 : if ($node->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP) != BBCODE_PARAGRAPH_ALLOW_BREAKUP || !count ($node->_children)) {
1325 0 : $dest_nodes[] =& $node;
1326 0 : return $dest_nodes;
1327 : }
1328 0 : $dest_node =& $node->duplicate ();
1329 0 : $nodecount = count ($node->_children);
1330 : // now this node allows breakup - do it
1331 0 : for ($i = 0; $i < $nodecount; $i++) {
1332 0 : $firstnode =& $node->_children[0];
1333 0 : $node->removeChild ($firstnode);
1334 0 : $sub_nodes =& $this->_breakupNodeByParagraphs ($firstnode);
1335 0 : for ($j = 0; $j < count ($sub_nodes); $j++) {
1336 0 : if ($j != 0) {
1337 0 : $dest_nodes[] =& $dest_node;
1338 0 : unset ($dest_node);
1339 0 : $dest_node =& $node->duplicate ();
1340 0 : }
1341 0 : $dest_node->appendChild ($sub_nodes[$j]);
1342 0 : }
1343 0 : unset ($sub_nodes);
1344 0 : }
1345 0 : $dest_nodes[] =& $dest_node;
1346 0 : return $dest_nodes;
1347 : }
1348 :
1349 : /**
1350 : * Is this node a usecontent node
1351 : * @access protected
1352 : * @param object $node The node to check
1353 : * @param bool $check_attrs Also check whether 'usecontent?'-attributes exist
1354 : * @return bool
1355 : */
1356 : function _isUseContent (&$node, $check_attrs = false) {
1357 0 : $name = $this->_getCanonicalName ($node->name ());
1358 : // this should NOT happen
1359 0 : if ($name === false) {
1360 0 : return false;
1361 : }
1362 0 : if ($this->_codes[$name]['callback_type'] == 'usecontent') {
1363 0 : return true;
1364 : }
1365 0 : $result = false;
1366 0 : if ($this->_codes[$name]['callback_type'] == 'callback_replace?') {
1367 0 : $result = true;
1368 0 : } else if ($this->_codes[$name]['callback_type'] != 'usecontent?') {
1369 0 : return false;
1370 : }
1371 0 : if ($check_attrs === false) {
1372 0 : return !$result;
1373 : }
1374 0 : $attributes = array_keys ($this->_topNodeVar ('_attributes'));
1375 0 : $p = @$this->_codes[$name]['callback_params']['usecontent_param'];
1376 0 : if (is_array ($p)) {
1377 0 : foreach ($p as $param) {
1378 0 : if (in_array ($param, $attributes)) {
1379 0 : return $result;
1380 : }
1381 0 : }
1382 0 : } else {
1383 0 : if (in_array ($p, $attributes)) {
1384 0 : return $result;
1385 : }
1386 : }
1387 0 : return !$result;
1388 : }
1389 :
1390 : /**
1391 : * Get canonical name of a code
1392 : *
1393 : * @access protected
1394 : * @param string $name
1395 : * @return string
1396 : */
1397 : function _getCanonicalName ($name) {
1398 0 : if (isset ($this->_codes[$name])) {
1399 0 : return $name;
1400 : }
1401 0 : $found = false;
1402 : // try to find the code in the code list
1403 0 : foreach (array_keys ($this->_codes) as $rname) {
1404 : // match
1405 0 : if (strtolower ($rname) == strtolower ($name)) {
1406 0 : $found = $rname;
1407 0 : break;
1408 : }
1409 0 : }
1410 0 : if ($found === false || ($this->_caseSensitive && $this->getCodeFlag ($found, 'case_sensitive', 'boolean', true))) {
1411 0 : return false;
1412 : }
1413 0 : return $rname;
1414 : }
1415 : }
1416 :
1417 : /**
1418 : * Node type: BBCode Element node
1419 : * @see StringParser_BBCode_Node_Element::_type
1420 : */
1421 1 : define ('STRINGPARSER_BBCODE_NODE_ELEMENT', 32);
1422 :
1423 : /**
1424 : * Node type: BBCode Paragraph node
1425 : * @see StringParser_BBCode_Node_Paragraph::_type
1426 : */
1427 1 : define ('STRINGPARSER_BBCODE_NODE_PARAGRAPH', 33);
1428 :
1429 :
1430 : /**
1431 : * BBCode String parser paragraph node class
1432 : *
1433 : * @package stringparser
1434 : */
1435 1 : class StringParser_BBCode_Node_Paragraph extends StringParser_Node {
1436 : /**
1437 : * The type of this node.
1438 : *
1439 : * This node is a bbcode paragraph node.
1440 : *
1441 : * @access protected
1442 : * @var int
1443 : * @see STRINGPARSER_BBCODE_NODE_PARAGRAPH
1444 : */
1445 : var $_type = STRINGPARSER_BBCODE_NODE_PARAGRAPH;
1446 :
1447 : /**
1448 : * Determines whether a criterium matches this node
1449 : *
1450 : * @access public
1451 : * @param string $criterium The criterium that is to be checked
1452 : * @param mixed $value The value that is to be compared
1453 : * @return bool True if this node matches that criterium
1454 : */
1455 : function matchesCriterium ($criterium, $value) {
1456 0 : if ($criterium == 'empty') {
1457 0 : if (!count ($this->_children)) {
1458 0 : return true;
1459 : }
1460 0 : if (count ($this->_children) > 1) {
1461 0 : return false;
1462 : }
1463 0 : if ($this->_children[0]->_type != STRINGPARSER_NODE_TEXT) {
1464 0 : return false;
1465 : }
1466 0 : if (!strlen ($this->_children[0]->content)) {
1467 0 : return true;
1468 : }
1469 0 : if (strlen ($this->_children[0]->content) > 2) {
1470 0 : return false;
1471 : }
1472 0 : $f_begin = $this->_children[0]->getFlag ('newlinemode.begin', 'integer', BBCODE_NEWLINE_PARSE);
1473 0 : $f_end = $this->_children[0]->getFlag ('newlinemode.end', 'integer', BBCODE_NEWLINE_PARSE);
1474 0 : $content = $this->_children[0]->content;
1475 0 : if ($f_begin != BBCODE_NEWLINE_PARSE && $content{0} == "\n") {
1476 0 : $content = substr ($content, 1);
1477 0 : }
1478 0 : if ($f_end != BBCODE_NEWLINE_PARSE && $content{strlen($content)-1} == "\n") {
1479 0 : $content = substr ($content, 0, -1);
1480 0 : }
1481 0 : if (!strlen ($content)) {
1482 0 : return true;
1483 : }
1484 0 : return false;
1485 : }
1486 0 : }
1487 : }
1488 :
1489 : /**
1490 : * BBCode String parser element node class
1491 : *
1492 : * @package stringparser
1493 : */
1494 1 : class StringParser_BBCode_Node_Element extends StringParser_Node {
1495 : /**
1496 : * The type of this node.
1497 : *
1498 : * This node is a bbcode element node.
1499 : *
1500 : * @access protected
1501 : * @var int
1502 : * @see STRINGPARSER_BBCODE_NODE_ELEMENT
1503 : */
1504 : var $_type = STRINGPARSER_BBCODE_NODE_ELEMENT;
1505 :
1506 : /**
1507 : * Element name
1508 : *
1509 : * @access protected
1510 : * @var string
1511 : * @see StringParser_BBCode_Node_Element::name
1512 : * @see StringParser_BBCode_Node_Element::setName
1513 : * @see StringParser_BBCode_Node_Element::appendToName
1514 : */
1515 : var $_name = '';
1516 :
1517 : /**
1518 : * Element flags
1519 : *
1520 : * @access protected
1521 : * @var array
1522 : */
1523 : var $_flags = array ();
1524 :
1525 : /**
1526 : * Element attributes
1527 : *
1528 : * @access protected
1529 : * @var array
1530 : */
1531 : var $_attributes = array ();
1532 :
1533 : /**
1534 : * Had a close tag
1535 : *
1536 : * @access protected
1537 : * @var bool
1538 : */
1539 : var $_hadCloseTag = false;
1540 :
1541 : /**
1542 : * Was processed by paragraph handling
1543 : *
1544 : * @access protected
1545 : * @var bool
1546 : */
1547 : var $_paragraphHandled = false;
1548 :
1549 : //////////////////////////////////////////////////
1550 :
1551 : /**
1552 : * Duplicate this node (but without children / parents)
1553 : *
1554 : * @access public
1555 : * @return object
1556 : */
1557 : function &duplicate () {
1558 0 : $newnode = new StringParser_BBCode_Node_Element ($this->occurredAt);
1559 0 : $newnode->_name = $this->_name;
1560 0 : $newnode->_flags = $this->_flags;
1561 0 : $newnode->_attributes = $this->_attributes;
1562 0 : $newnode->_hadCloseTag = $this->_hadCloseTag;
1563 0 : $newnode->_paragraphHandled = $this->_paragraphHandled;
1564 0 : $newnode->_codeInfo = $this->_codeInfo;
1565 0 : return $newnode;
1566 : }
1567 :
1568 : /**
1569 : * Retreive name of this element
1570 : *
1571 : * @access public
1572 : * @return string
1573 : */
1574 : function name () {
1575 0 : return $this->_name;
1576 : }
1577 :
1578 : /**
1579 : * Set name of this element
1580 : *
1581 : * @access public
1582 : * @param string $name The new name of the element
1583 : */
1584 : function setName ($name) {
1585 0 : $this->_name = $name;
1586 0 : return true;
1587 : }
1588 :
1589 : /**
1590 : * Append to name of this element
1591 : *
1592 : * @access public
1593 : * @param string $chars The chars to append to the name of the element
1594 : */
1595 : function appendToName ($chars) {
1596 0 : $this->_name .= $chars;
1597 0 : return true;
1598 : }
1599 :
1600 : /**
1601 : * Append to attribute of this element
1602 : *
1603 : * @access public
1604 : * @param string $name The name of the attribute
1605 : * @param string $chars The chars to append to the attribute of the element
1606 : */
1607 : function appendToAttribute ($name, $chars) {
1608 0 : if (!isset ($this->_attributes[$name])) {
1609 0 : $this->_attributes[$name] = $chars;
1610 0 : return true;
1611 : }
1612 0 : $this->_attributes[$name] .= $chars;
1613 0 : return true;
1614 : }
1615 :
1616 : /**
1617 : * Set attribute
1618 : *
1619 : * @access public
1620 : * @param string $name The name of the attribute
1621 : * @param string $value The new value of the attribute
1622 : */
1623 : function setAttribute ($name, $value) {
1624 0 : $this->_attributes[$name] = $value;
1625 0 : return true;
1626 : }
1627 :
1628 : /**
1629 : * Set code info
1630 : *
1631 : * @access public
1632 : * @param array $info The code info array
1633 : */
1634 : function setCodeInfo ($info) {
1635 0 : $this->_codeInfo = $info;
1636 0 : $this->_flags = $info['flags'];
1637 0 : return true;
1638 : }
1639 :
1640 : /**
1641 : * Get attribute value
1642 : *
1643 : * @access public
1644 : * @param string $name The name of the attribute
1645 : */
1646 : function attribute ($name) {
1647 0 : if (!isset ($this->_attributes[$name])) {
1648 0 : return null;
1649 : }
1650 0 : return $this->_attributes[$name];
1651 : }
1652 :
1653 : /**
1654 : * Set flag that this element had a close tag
1655 : *
1656 : * @access public
1657 : */
1658 : function setHadCloseTag () {
1659 0 : $this->_hadCloseTag = true;
1660 0 : }
1661 :
1662 : /**
1663 : * Set flag that this element was already processed by paragraph handling
1664 : *
1665 : * @access public
1666 : */
1667 : function setParagraphHandled () {
1668 0 : $this->_paragraphHandled = true;
1669 0 : }
1670 :
1671 : /**
1672 : * Get flag if this element was already processed by paragraph handling
1673 : *
1674 : * @access public
1675 : * @return bool
1676 : */
1677 : function paragraphHandled () {
1678 0 : return $this->_paragraphHandled;
1679 : }
1680 :
1681 : /**
1682 : * Get flag if this element had a close tag
1683 : *
1684 : * @access public
1685 : * @return bool
1686 : */
1687 : function hadCloseTag () {
1688 0 : return $this->_hadCloseTag;
1689 : }
1690 :
1691 : /**
1692 : * Determines whether a criterium matches this node
1693 : *
1694 : * @access public
1695 : * @param string $criterium The criterium that is to be checked
1696 : * @param mixed $value The value that is to be compared
1697 : * @return bool True if this node matches that criterium
1698 : */
1699 : function matchesCriterium ($criterium, $value) {
1700 0 : if ($criterium == 'tagName') {
1701 0 : return ($value == $this->_name);
1702 : }
1703 0 : if ($criterium == 'needsTextNodeModification') {
1704 0 : return (($this->getFlag ('opentag.before.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || $this->getFlag ('opentag.after.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || ($this->_hadCloseTag && ($this->getFlag ('closetag.before.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || $this->getFlag ('closetag.after.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE))) == (bool)$value);
1705 : }
1706 0 : if (substr ($criterium, 0, 5) == 'flag:') {
1707 0 : $criterium = substr ($criterium, 5);
1708 0 : return ($this->getFlag ($criterium) == $value);
1709 : }
1710 0 : if (substr ($criterium, 0, 6) == '!flag:') {
1711 0 : $criterium = substr ($criterium, 6);
1712 0 : return ($this->getFlag ($criterium) != $value);
1713 : }
1714 0 : if (substr ($criterium, 0, 6) == 'flag=:') {
1715 0 : $criterium = substr ($criterium, 6);
1716 0 : return ($this->getFlag ($criterium) === $value);
1717 : }
1718 0 : if (substr ($criterium, 0, 7) == '!flag=:') {
1719 0 : $criterium = substr ($criterium, 7);
1720 0 : return ($this->getFlag ($criterium) !== $value);
1721 : }
1722 0 : return parent::matchesCriterium ($criterium, $value);
1723 : }
1724 :
1725 : /**
1726 : * Get first child if it is a text node
1727 : *
1728 : * @access public
1729 : * @return mixed
1730 : */
1731 : function &firstChildIfText () {
1732 0 : $ret =& $this->firstChild ();
1733 0 : if (is_null ($ret)) {
1734 0 : return $ret;
1735 : }
1736 0 : if ($ret->_type != STRINGPARSER_NODE_TEXT) {
1737 : // DON'T DO $ret = null WITHOUT unset BEFORE!
1738 : // ELSE WE WILL ERASE THE NODE ITSELF! EVIL!
1739 0 : unset ($ret);
1740 0 : $ret = null;
1741 0 : }
1742 0 : return $ret;
1743 : }
1744 :
1745 : /**
1746 : * Get last child if it is a text node AND if this element had a close tag
1747 : *
1748 : * @access public
1749 : * @return mixed
1750 : */
1751 : function &lastChildIfText () {
1752 0 : $ret =& $this->lastChild ();
1753 0 : if (is_null ($ret)) {
1754 0 : return $ret;
1755 : }
1756 0 : if ($ret->_type != STRINGPARSER_NODE_TEXT || !$this->_hadCloseTag) {
1757 : // DON'T DO $ret = null WITHOUT unset BEFORE!
1758 : // ELSE WE WILL ERASE THE NODE ITSELF! EVIL!
1759 0 : if ($ret->_type != STRINGPARSER_NODE_TEXT && !$ret->hadCloseTag ()) {
1760 0 : $ret2 =& $ret->_findPrevAdjentTextNodeHelper ();
1761 0 : unset ($ret);
1762 0 : $ret =& $ret2;
1763 0 : unset ($ret2);
1764 0 : } else {
1765 0 : unset ($ret);
1766 0 : $ret = null;
1767 : }
1768 0 : }
1769 0 : return $ret;
1770 : }
1771 :
1772 : /**
1773 : * Find next adjent text node after close tag
1774 : *
1775 : * returns the node or null if none exists
1776 : *
1777 : * @access public
1778 : * @return mixed
1779 : */
1780 : function &findNextAdjentTextNode () {
1781 0 : $ret = null;
1782 0 : if (is_null ($this->_parent)) {
1783 0 : return $ret;
1784 : }
1785 0 : if (!$this->_hadCloseTag) {
1786 0 : return $ret;
1787 : }
1788 0 : $ccount = count ($this->_parent->_children);
1789 0 : $found = false;
1790 0 : for ($i = 0; $i < $ccount; $i++) {
1791 0 : if ($this->_parent->_children[$i]->equals ($this)) {
1792 0 : $found = $i;
1793 0 : break;
1794 : }
1795 0 : }
1796 0 : if ($found === false) {
1797 0 : return $ret;
1798 : }
1799 0 : if ($found < $ccount - 1) {
1800 0 : if ($this->_parent->_children[$found+1]->_type == STRINGPARSER_NODE_TEXT) {
1801 0 : return $this->_parent->_children[$found+1];
1802 : }
1803 0 : return $ret;
1804 : }
1805 0 : if ($this->_parent->_type == STRINGPARSER_BBCODE_NODE_ELEMENT && !$this->_parent->hadCloseTag ()) {
1806 0 : $ret =& $this->_parent->findNextAdjentTextNode ();
1807 0 : return $ret;
1808 : }
1809 0 : return $ret;
1810 : }
1811 :
1812 : /**
1813 : * Find previous adjent text node before open tag
1814 : *
1815 : * returns the node or null if none exists
1816 : *
1817 : * @access public
1818 : * @return mixed
1819 : */
1820 : function &findPrevAdjentTextNode () {
1821 0 : $ret = null;
1822 0 : if (is_null ($this->_parent)) {
1823 0 : return $ret;
1824 : }
1825 0 : $ccount = count ($this->_parent->_children);
1826 0 : $found = false;
1827 0 : for ($i = 0; $i < $ccount; $i++) {
1828 0 : if ($this->_parent->_children[$i]->equals ($this)) {
1829 0 : $found = $i;
1830 0 : break;
1831 : }
1832 0 : }
1833 0 : if ($found === false) {
1834 0 : return $ret;
1835 : }
1836 0 : if ($found > 0) {
1837 0 : if ($this->_parent->_children[$found-1]->_type == STRINGPARSER_NODE_TEXT) {
1838 0 : return $this->_parent->_children[$found-1];
1839 : }
1840 0 : if (!$this->_parent->_children[$found-1]->hadCloseTag ()) {
1841 0 : $ret =& $this->_parent->_children[$found-1]->_findPrevAdjentTextNodeHelper ();
1842 0 : }
1843 0 : return $ret;
1844 : }
1845 0 : return $ret;
1846 : }
1847 :
1848 : /**
1849 : * Helper function for findPrevAdjentTextNode
1850 : *
1851 : * Looks at the last child node; if it's a text node, it returns it,
1852 : * if the element node did not have an open tag, it calls itself
1853 : * recursively.
1854 : */
1855 : function &_findPrevAdjentTextNodeHelper () {
1856 0 : $lastnode =& $this->lastChild ();
1857 0 : if ($lastnode === null || $lastnode->_type == STRINGPARSER_NODE_TEXT) {
1858 0 : return $lastnode;
1859 : }
1860 0 : if (!$lastnode->hadCloseTag ()) {
1861 0 : $ret =& $lastnode->_findPrevAdjentTextNodeHelper ();
1862 0 : } else {
1863 0 : $ret = null;
1864 : }
1865 0 : return $ret;
1866 : }
1867 :
1868 : /**
1869 : * Get Flag
1870 : *
1871 : * @access public
1872 : * @param string $flag The requested flag
1873 : * @param string $type The requested type of the return value
1874 : * @param mixed $default The default return value
1875 : * @return mixed
1876 : */
1877 : function getFlag ($flag, $type = 'mixed', $default = null) {
1878 0 : if (!isset ($this->_flags[$flag])) {
1879 0 : return $default;
1880 : }
1881 0 : $return = $this->_flags[$flag];
1882 0 : if ($type != 'mixed') {
1883 0 : settype ($return, $type);
1884 0 : }
1885 0 : return $return;
1886 : }
1887 :
1888 : /**
1889 : * Set a flag
1890 : *
1891 : * @access public
1892 : * @param string $name The name of the flag
1893 : * @param mixed $value The value of the flag
1894 : */
1895 : function setFlag ($name, $value) {
1896 0 : $this->_flags[$name] = $value;
1897 0 : return true;
1898 : }
1899 :
1900 : /**
1901 : * Validate code
1902 : *
1903 : * @access public
1904 : * @param string $action The action which is to be called ('validate'
1905 : * for first validation, 'validate_again' for
1906 : * second validation (optional))
1907 : * @return bool
1908 : */
1909 : function validate ($action = 'validate') {
1910 0 : if ($action != 'validate' && $action != 'validate_again') {
1911 0 : return false;
1912 : }
1913 0 : if ($this->_codeInfo['callback_type'] != 'simple_replace' && $this->_codeInfo['callback_type'] != 'simple_replace_single') {
1914 0 : if (!is_callable ($this->_codeInfo['callback_func'])) {
1915 0 : return false;
1916 : }
1917 :
1918 0 : if (($this->_codeInfo['callback_type'] == 'usecontent' || $this->_codeInfo['callback_type'] == 'usecontent?' || $this->_codeInfo['callback_type'] == 'callback_replace?') && count ($this->_children) == 1 && $this->_children[0]->_type == STRINGPARSER_NODE_TEXT) {
1919 : // we have to make sure the object gets passed on as a reference
1920 : // if we do call_user_func(..., &$this) this will clash with PHP5
1921 0 : $callArray = array ($action, $this->_attributes, $this->_children[0]->content, $this->_codeInfo['callback_params']);
1922 0 : $callArray[] =& $this;
1923 0 : $res = call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
1924 0 : if ($res) {
1925 : // ok, now, if we've got a usecontent type, set a flag that
1926 : // this may not be broken up by paragraph handling!
1927 : // but PLEASE do NOT change if already set to any other setting
1928 : // than BBCODE_PARAGRAPH_ALLOW_BREAKUP because we could
1929 : // override e.g. BBCODE_PARAGRAPH_BLOCK_ELEMENT!
1930 0 : $val = $this->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP);
1931 0 : if ($val == BBCODE_PARAGRAPH_ALLOW_BREAKUP) {
1932 0 : $this->_flags['paragraph_type'] = BBCODE_PARAGRAPH_ALLOW_INSIDE;
1933 0 : }
1934 0 : }
1935 0 : return $res;
1936 : }
1937 :
1938 : // we have to make sure the object gets passed on as a reference
1939 : // if we do call_user_func(..., &$this) this will clash with PHP5
1940 0 : $callArray = array ($action, $this->_attributes, null, $this->_codeInfo['callback_params']);
1941 0 : $callArray[] =& $this;
1942 0 : return call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
1943 : }
1944 0 : return (bool)(!count ($this->_attributes));
1945 : }
1946 :
1947 : /**
1948 : * Get replacement for this code
1949 : *
1950 : * @access public
1951 : * @param string $subcontent The content of all sub-nodes
1952 : * @return string
1953 : */
1954 : function getReplacement ($subcontent) {
1955 0 : if ($this->_codeInfo['callback_type'] == 'simple_replace' || $this->_codeInfo['callback_type'] == 'simple_replace_single') {
1956 0 : if ($this->_codeInfo['callback_type'] == 'simple_replace_single') {
1957 0 : if (strlen ($subcontent)) { // can't be!
1958 0 : return false;
1959 : }
1960 0 : return $this->_codeInfo['callback_params']['start_tag'];
1961 : }
1962 0 : return $this->_codeInfo['callback_params']['start_tag'].$subcontent.$this->_codeInfo['callback_params']['end_tag'];
1963 : }
1964 : // else usecontent, usecontent? or callback_replace or callback_replace_single
1965 : // => call function (the function is callable, determined in validate()!)
1966 :
1967 : // we have to make sure the object gets passed on as a reference
1968 : // if we do call_user_func(..., &$this) this will clash with PHP5
1969 0 : $callArray = array ('output', $this->_attributes, $subcontent, $this->_codeInfo['callback_params']);
1970 0 : $callArray[] =& $this;
1971 0 : return call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
1972 : }
1973 :
1974 : /**
1975 : * Dump this node to a string
1976 : *
1977 : * @access protected
1978 : * @return string
1979 : */
1980 : function _dumpToString () {
1981 0 : $str = "bbcode \"".substr (preg_replace ('/\s+/', ' ', $this->_name), 0, 40)."\"";
1982 0 : if (count ($this->_attributes)) {
1983 0 : $attribs = array_keys ($this->_attributes);
1984 0 : sort ($attribs);
1985 0 : $str .= ' (';
1986 0 : $i = 0;
1987 0 : foreach ($attribs as $attrib) {
1988 0 : if ($i != 0) {
1989 0 : $str .= ', ';
1990 0 : }
1991 0 : $str .= $attrib.'="';
1992 0 : $str .= substr (preg_replace ('/\s+/', ' ', $this->_attributes[$attrib]), 0, 10);
1993 0 : $str .= '"';
1994 0 : $i++;
1995 0 : }
1996 0 : $str .= ')';
1997 0 : }
1998 0 : return $str;
1999 : }
2000 : }
|